﻿using System.Linq;
using System.Reflection;
using Ionic.Zip;
using Octokit;
using System;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace XrmToolBox.Updater
{
    public partial class UpdateForm : Form
    {
        public string XrmToolBoxPath { get; private set; }
        public UpdateForm()
        {
            InitializeComponent();
        }

        private async Task Start()
        {
            var isClose = Process.GetProcessesByName("XrmToolBox").Length > 0 && ConfirmationDialog("XrmToolBox is running. Click OK to close it, and start the update process", "XRMToolBox running..");
            var isXrmToolBoxRunning = false;
            if (isClose)
            {
                foreach (var xrmToolBoxInstance in Process.GetProcessesByName("XrmToolBox"))
                {
                    XrmToolBoxPath = xrmToolBoxInstance.Modules[0].FileName;
                    xrmToolBoxInstance.CloseMainWindow();
                }

                isXrmToolBoxRunning = Process.GetProcessesByName("XrmToolBox").Length > 0 &&
                                      !ConfirmationDialog(
                                          "Unable to close XRMToolBox. Manually close the application and click OK to the update process",
                                          "Unable to close XRMToolBox..");
            }
            if (!isXrmToolBoxRunning)
            {
                var githubClient = new GitHubClient(new ProductHeaderValue("XrmToolBox.Autoupdater"));
                UpdateStatusLabel("Getting list of releases..");
                var releases = await githubClient.Release.GetAll("MscrmTools", "XrmToolBox");
                releases = releases.Where(x => !x.Prerelease).ToList();
                UpdateStatusLabel(string.Format("Getting assets for release {0}..", releases[0].TagName));
                var assets = await githubClient.Release.GetAllAssets("MscrmTools", "XrmToolBox", releases[0].Id);
                if (IsUpdateRequired(releases[0].TagName))
                {
                    DownloadLatestRelease(assets[0], releases[0].TagName);
                }
                else
                {
                    UpdateStatusLabel(string.Format("You are already running XRMToolBox {0}. No update required.", releases[0].TagName));
                    updateProgressBar.Value = updateProgressBar.Maximum;
                    updateProgressBar.Style = ProgressBarStyle.Continuous;
                }
            }
            else
            {
                UpdateStatusLabel("Update cancelled");
                updateProgressBar.Value = updateProgressBar.Maximum;
                updateProgressBar.Style = ProgressBarStyle.Continuous;
            }
        }

        private bool ConfirmationDialog(string message, string title)
        {
            return  DialogResult.OK == MessageBox.Show(this, message, title, MessageBoxButtons.OKCancel);
        }

        private bool IsUpdateRequired(string downloadVersion)
        {
            var isUpdate = false;
            var xrmToolBoxConfigLocation = ConfigurationManager.AppSettings["XrmToolBoxLocation"];
            if (!xrmToolBoxConfigLocation.EndsWith("\\"))
            {
                xrmToolBoxConfigLocation += "\\";
            }
            if (string.IsNullOrEmpty(XrmToolBoxPath) &&
                !string.IsNullOrEmpty(xrmToolBoxConfigLocation))
            {
                XrmToolBoxPath = string.Format(@"{0}XrmToolBox.exe", xrmToolBoxConfigLocation);
            }

            if (string.IsNullOrEmpty(XrmToolBoxPath))
            {
                DisplayMessageBox("Unable to locate file path of XRMToolBox. Set this in the config file",
                    "XRMToolBox path not found");
                UpdateStatusLabel("Unable to locate file path of XRMToolBox. Set this in the config file");
                return false;
            }

            if (File.Exists(XrmToolBoxPath))
            {
                var xrmToolBoxCurrent = FileVersionInfo.GetVersionInfo(XrmToolBoxPath);
                var fileVersion = xrmToolBoxCurrent.ProductVersion.Split('.');
                var recentVersion = downloadVersion.Substring(2).Split('.');
                for (var i = 0; i < fileVersion.Length; i++)
                {
                    isUpdate = int.Parse(fileVersion[i]) != int.Parse(recentVersion[i]);
                    if (isUpdate)
                    {
                        break;
                    }
                }
            }
            return isUpdate;
        }

        private void DownloadLatestRelease(ReleaseAsset latestRelease, string downloadVersion)
        {
            var xrmtoolboxZipFileName = string.Format("XrmToolBox-{0}.zip", downloadVersion);
            using (var webClient = new WebClient())
            {
                UpdateStatusLabel("Downloading release file..");
                webClient.DownloadProgressChanged += (o, pe) =>
                {
                    UpdateStatusLabel(string.Format("Downloading {3} : {0} KB of {1} KB. {2}% complete...",
                                            pe.BytesReceived / 1024, pe.TotalBytesToReceive / 1024, pe.ProgressPercentage, downloadVersion));
                    if (updateProgressBar.InvokeRequired)
                    {
                        updateProgressBar.Invoke(new Action(() => updateProgressBar.Value = pe.ProgressPercentage));
                    }
                    else
                    {
                        updateProgressBar.Value = pe.ProgressPercentage;
                    }
                };
                webClient.DownloadFileCompleted += (o, ace) =>
                {
                    updateProgressBar.Visible = false;
                    if (ace.Error != null && !string.IsNullOrEmpty(ace.Error.Message))
                    {
                        UpdateStatusLabel("Download File: " + ace.Error.Message);
                        return;
                    }
                    UpdateStatusLabel(string.Format("Begin extracting {0}", xrmtoolboxZipFileName));

                    Task.Run(() =>
                    {
                        foreach (var file in ZipFile.Read(xrmtoolboxZipFileName).Entries)
                        {
                            file.Extract(XrmToolBoxPath.Substring(0, XrmToolBoxPath.LastIndexOf("\\")),
                                ExtractExistingFileAction.OverwriteSilently);
                        }
                    }).ConfigureAwait(false);

                    UpdateStatusLabel(string.Format("Extract of {0} complete..", xrmtoolboxZipFileName));
                    DisplayMessageBox(string.Format("XrmToolBox successfully updated to {0}.", downloadVersion),
                        "Update Complete");
                };
                webClient.DownloadFileAsync(new Uri(latestRelease.BrowserDownloadUrl), xrmtoolboxZipFileName);
            }             
        }

        private void DisplayMessageBox(string message,string title)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new Action(() => MessageBox.Show(this, message, title, MessageBoxButtons.OK, MessageBoxIcon.Information)));
            }
            else
            {
                MessageBox.Show(this, message, title, MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }

        private void UpdateStatusLabel(string statusMessage)
        {
            if (statusLabel.InvokeRequired)
            {
                statusLabel.Invoke(new Action(() => statusLabel.Text = statusMessage));
            }
            else
            {
                statusLabel.Text = statusMessage;
            }
        }

        private async void UpdateForm_Load(object sender, EventArgs e)
        {
            try
            {
                this.Text = Assembly.GetExecutingAssembly().GetName().Name + " v" +Assembly.GetExecutingAssembly().GetName().Version;
                await Start();
            }
            catch (Exception ex)
            {
                updateProgressBar.Value = updateProgressBar.Maximum;
                updateProgressBar.Style = ProgressBarStyle.Continuous;
                UpdateStatusLabel(string.Format("Exception occured -> {0}", ex.Message));
            }
        }
    }
}
